﻿using Fasterflect;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
using Loong.Dependency;
using Loong.Json;
using Loong.Logging;
using Loong.Threading.BackgroundWorkers;

namespace Loong.BackgroundJobs
{
    public class BackgroundJobWorker : PeriodicBackgroundWorkerBase, IBackgroundJobWorker, ISingletonDependency
    {
        public static TimeSpan JobPollPeriod { get; set; }

        private readonly IServiceProvider _serviceProvider;

        static BackgroundJobWorker()
        {
            JobPollPeriod = TimeSpan.FromSeconds(5);
        }

        public BackgroundJobWorker(
            IServiceProvider serviceProvider,
            ILogger<BackgroundJobWorker> logger)
            : base(logger)
        {
            _serviceProvider = serviceProvider;

            Period = JobPollPeriod;
        }

        protected override async Task DoWorkAsync()
        {
            var waitingJob = await UseStoreAsync(store => store.GetAndLockWaitingJobAsync());
            if (waitingJob == null)
            {
                Period = JobPollPeriod;
                return;
            }

            await TryProcessJob(waitingJob);

            Period = TimeSpan.FromMilliseconds(15);
        }

        private async Task TryProcessJob(BackgroundJobInfo jobInfo)
        {
            try
            {
                jobInfo.TryCount++;
                jobInfo.LastTryTime = DateTime.Now;

                var jobType = Type.GetType(jobInfo.JobType);
                using (var scope = _serviceProvider.CreateScope())
                {
                    try
                    {
                        var job = scope.ServiceProvider.GetRequiredService(jobType);
                        var jobExecuteMethod = jobType.GetMethod("ExecuteAsync");
                        var argsType = jobExecuteMethod.GetParameters()[0].ParameterType;
                        var argsObj = jobInfo.JobArgs.FromJsonString(argsType);

                        var executeAsync = jobType.DelegateForCallMethod("ExecuteAsync", argsType);
                        await (executeAsync(job, argsObj) as Task);

                        await UseStoreAsync(store => store.DeleteJobAsync(jobInfo));
                    }
                    catch (Exception ex)
                    {
                        Logger.LogException(ex);

                        var nextTryTime = jobInfo.CalculateNextTryTime();
                        if (nextTryTime.HasValue)
                        {
                            jobInfo.NextTryTime = nextTryTime.Value;
                        }
                        else
                        {
                            jobInfo.IsAbandoned = true;
                        }

                        await TryUpdateAsync(jobInfo);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);

                jobInfo.IsAbandoned = true;

                await TryUpdateAsync(jobInfo);
            }
        }

        private async Task TryUpdateAsync(BackgroundJobInfo jobInfo)
        {
            try
            {
                jobInfo.IsLocked = false;
                jobInfo.LockEndTime = null;

                await UseStoreAsync(store => store.UpdateJobAsync(jobInfo));
            }
            catch (Exception updateEx)
            {
                Logger.LogException(updateEx);
            }
        }

        private async Task UseStoreAsync(Func<IBackgroundJobStore, Task> func)
        {
            using (var scope = _serviceProvider.CreateScope())
            {
                var store = scope.ServiceProvider.GetRequiredService<IBackgroundJobStore>();
                await func(store);
            }
        }

        private async Task<TResult> UseStoreAsync<TResult>(Func<IBackgroundJobStore, Task<TResult>> func)
        {
            using (var scope = _serviceProvider.CreateScope())
            {
                var store = scope.ServiceProvider.GetRequiredService<IBackgroundJobStore>();
                return await func(store);
            }
        }
    }
}
